package elephant.rpc.server.service;

import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * 
 * @author skydu
 *
 */
public class RPCServiceManager {
	//
	private static Logger logger=LoggerFactory.getLogger(RPCServiceManager.class);
	//
	private Map<String,Object> serviceMap;
	private Map<String,MethodStub> methodMap;
	private RPCServiceInterceptor interceptor;
	//
	public RPCServiceManager(){
		serviceMap=new HashMap<>();
		methodMap=new HashMap<>();
	}
	//
	public void registerService(Class<?> interfaceClass,Object service){
		if(interfaceClass==null){
			throw new IllegalArgumentException("registerService failed."+interfaceClass
					+" cannot be null."); 
		}
		if(service==null){
			throw new IllegalArgumentException("registerService failed."
					+"service cannot be null."); 
		}
		String name=interfaceClass.getSimpleName();
		if(!interfaceClass.isInterface()){
			throw new IllegalArgumentException("registerService failed."+interfaceClass
					+" is not Interface."); 
		}
		if(!interfaceClass.isAssignableFrom(service.getClass())){
			throw new IllegalArgumentException("registerService failed."
					+" service instance is not isAssignableFrom "+interfaceClass.getSimpleName()); 
		}
		if(serviceMap.containsKey(name)){
			throw new IllegalArgumentException("object with name:"+name
					+" already exists."); 
		}
		if(logger.isInfoEnabled()){
			logger.info("register service "+name);
		}
		serviceMap.put(name,service);
		//
		registerMethod(interfaceClass,service);
	}
	//
	private void registerMethod(Class<?> interfaceClass,Object service){
		String className=interfaceClass.getSimpleName();
		for(Method m:interfaceClass.getDeclaredMethods()){
	        if(!Modifier.isPublic(m.getModifiers())){
	        	continue;
	        }
	        if(Modifier.isStatic(m.getModifiers())){
	        	continue;
	        }
	        Method implMethod=null;
	        try {
	        	implMethod=service.getClass().getMethod(m.getName(),
	        			m.getParameterTypes());
	        	if(implMethod==null){
	        		throw new IllegalArgumentException("impl service cannot find method "+m.getName());
	        	}
	        } catch (Exception e) {
				logger.error(e.getMessage(), e);
				throw new IllegalArgumentException("impl service cannot find method "+m.getName());
			}
	        String[] ps=getParameterTypes(m);
			String methodName=getMethodName(className,m.getName(),ps);
			methodMap.put(methodName,new MethodStub(methodName,m,implMethod));
			//
			if(logger.isInfoEnabled()){
				logger.info("register method {}",methodName);
			}
		}
	}
	//
	public String[] getParameterTypes(Method m){
		String[] ps=new String[m.getParameterTypes().length];
		for(int i=0;i<ps.length;i++){
			ps[i]=m.getParameterTypes()[i].getSimpleName();
		}
		return ps;
	}
	//
	private String getMethodName(
			String className,
			String methodName,
			String[] paraClassName){
		StringBuilder sb=new StringBuilder();
		sb.append(className);
		sb.append(".");
		sb.append(methodName);
		sb.append("#");
		for(String name:paraClassName){
			sb.append(name);	
			sb.append(",");
		}
		return sb.toString();
	}
	//
	public Object getService(String serviceClassName){
		return serviceMap.get(serviceClassName);
	}
	//
	public Object getService(Class<?> interfaceClass){
		return serviceMap.get(interfaceClass.getSimpleName());
	}
	//
	public MethodStub getMethod(String className,String methodName,String[] paraClassName){
		return methodMap.get(getMethodName(className, methodName, paraClassName));
	}
	//
	public List<MethodStub>getMethodNames(){
		List<MethodStub> list=new ArrayList<>(methodMap.values());
		Collections.sort(list,(o1, o2)->{
			return o1.methodName.compareTo(o2.methodName);
		});
		return list;
	}
	//
	public Object invoke(String interfaceClassName,String methodName,String[] parameterTypes,Object[] args)
	throws Throwable{
		MethodStub stub=getMethod(interfaceClassName,methodName,parameterTypes);
		if(stub==null){
			throw new IllegalArgumentException("method not found."+interfaceClassName+"."+methodName);
		}
		return invoke(stub.method.getDeclaringClass(),stub, args);
	}
	//
	public Object invoke(Class<?> interfaceClass,Method m,Object[] args) 
	throws Throwable{
		MethodStub stub=getMethod(m.getDeclaringClass().getSimpleName(),m.getName(),
				getParameterTypes(m));
		return invoke(interfaceClass,stub, args);
	}
	//
	public Object invoke(Class<?> interfaceClass,MethodStub stub,Object[] args) 
	throws Throwable{
		Object service=getService(interfaceClass);
		Throwable e=null;
		try{
			if(interceptor!=null){
				interceptor.beforeInvoke(interfaceClass,stub, args);
			}
			return stub.method.invoke(service,args);
		}catch (Throwable ex) {
			e=ex;
			throw e;
		}finally {
			if(interceptor!=null){
				interceptor.afterInvoke(interfaceClass,stub,args,e);
			}
		}
	}
	//
	/**
	 * @return the interceptor
	 */
	public RPCServiceInterceptor getInterceptor() {
		return interceptor;
	}
	/**
	 * @param interceptor the interceptor to set
	 */
	public void setInterceptor(RPCServiceInterceptor interceptor) {
		this.interceptor = interceptor;
	}
	
}
